package cn.jbolt.core.gen;

import cn.hutool.core.util.StrUtil;
import cn.jbolt.core.util.JBoltConsoleUtil;
import com.jfinal.kit.LogKit;
import com.jfinal.kit.StrKit;
import com.jfinal.plugin.activerecord.generator.ColumnMeta;
import com.jfinal.plugin.activerecord.generator.MetaBuilder;
import com.jfinal.plugin.activerecord.generator.TableMeta;

import javax.sql.DataSource;
import java.sql.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * JBoltMetaBuilder
 */
public class JBoltMetaBuilder extends MetaBuilder {
	private String schemaPattern;
	protected Map<String, ColumnMeta> colMap;

	public JBoltMetaBuilder(DataSource dataSource) {
		super(dataSource);
		colMap = new HashMap<String, ColumnMeta>();
	}

	@Override
	public List<TableMeta> build() {
		JBoltConsoleUtil.printMessageWithDate(" Build TableMeta ...");
		try {
			conn = dataSource.getConnection();
			if(StrKit.isBlank(schemaPattern)){
				String connSchema = conn.getSchema();
				if(StrKit.notBlank(connSchema)){
					setSchemaPattern(connSchema);
				}
			}
			dbMeta = conn.getMetaData();

			List<TableMeta> ret = new ArrayList<TableMeta>();
			buildTableNames(ret);
			for (TableMeta tableMeta : ret) {
				buildPrimaryKey(tableMeta);
				buildColumnMetas(tableMeta);
			}
			rebuildColumnMetas(ret);
			removeNoPrimaryKeyTable(ret);

			return ret;
		} catch (SQLException e) {
			throw new RuntimeException(e);
		} finally {
			if (conn != null) {
				try {
					conn.close();
				} catch (SQLException e) {
					throw new RuntimeException(e);
				}
			}
		}
	}

	protected void rebuildColumnMetas(List<TableMeta> tableMetas) {
		Connection conn = null;
		String colName;
		try {
			conn = dataSource.getConnection();
			DatabaseMetaData dbMeta = conn.getMetaData();
			for (TableMeta tableMeta : tableMetas) {
				// 通过查看 dbMeta.getColumns(...) 源码注释，还可以获取到更多 meta data
				ResultSet rs = dbMeta.getColumns(conn.getCatalog(), schemaPattern, tableMeta.name, null);
				while (rs.next()) {
					colName = rs.getString("COLUMN_NAME"); // 名称
					ColumnMeta columnMeta = colMap.get((tableMeta.baseModelName + "_" + colName).toLowerCase());
					if (columnMeta != null) {
						columnMeta.type = rs.getString("TYPE_NAME"); // 类型
						if (columnMeta.type == null) {
							columnMeta.type = "";
						}
						if (!columnMeta.type.equalsIgnoreCase("timestamp(6)") && columnMeta.type.indexOf("(") == -1
								&& columnMeta.type.indexOf(")") == -1) {
							int columnSize = rs.getInt("COLUMN_SIZE"); // 长度
							if (columnSize > 0) {
								columnMeta.type = columnMeta.type + "(" + columnSize;
								int decimalDigits = rs.getInt("DECIMAL_DIGITS"); // 小数位数
								if (decimalDigits > 0) {
									columnMeta.type = columnMeta.type + "," + decimalDigits;
								}
								columnMeta.type = columnMeta.type + ")";
							}
						}
						columnMeta.isPrimaryKey = "   ";
						String[] keys = tableMeta.primaryKey.split(",");
						for (String key : keys) {
							if (key.equalsIgnoreCase(columnMeta.name)) {
								columnMeta.isPrimaryKey = "PRI";
								break;
							}
						}
						if (StrKit.isBlank(columnMeta.remarks)) {
							columnMeta.remarks = rs.getString("REMARKS"); // 备注
						}
						if (columnMeta.remarks == null) {
							columnMeta.remarks = "";
						}

						columnMeta.defaultValue = rs.getString("COLUMN_DEF"); // 默认值
						if (columnMeta.defaultValue == null) {
							columnMeta.defaultValue = "";
						}

						columnMeta.isNullable = rs.getString("IS_NULLABLE"); // 是否允许 NULL 值
						if (columnMeta.isNullable == null) {
							columnMeta.isNullable = "";
						}

						if (tableMeta.colNameMaxLen < columnMeta.name.length()) {
							tableMeta.colNameMaxLen = columnMeta.name.length();
						}
						if (tableMeta.colTypeMaxLen < columnMeta.type.length()) {
							tableMeta.colTypeMaxLen = columnMeta.type.length();
						}
						if (tableMeta.colDefaultValueMaxLen < columnMeta.defaultValue.length()) {
							tableMeta.colDefaultValueMaxLen = columnMeta.defaultValue.length();
						}

					}

				}
				rs.close();
			}
		} catch (SQLException e) {
			throw new RuntimeException(e);
		} finally {
			if (conn != null) {
				try {
					conn.close();
				} catch (SQLException e) {
					LogKit.error(e.getMessage(), e);
				}
			}
		}
	}

	@Override
	protected void buildColumnMetas(TableMeta tableMeta) throws SQLException {
		String sql = dialect.forTableBuilderDoBuild(tableMeta.name);
		Statement stm = conn.createStatement();
		ResultSet rs = stm.executeQuery(sql);
		ResultSetMetaData rsmd = rs.getMetaData();
		int columnCount = rsmd.getColumnCount();

		Map<String, ColumnMeta> columnMetaMap = new HashMap<>();
		if (generateRemarks) {
			ResultSet colMetaRs = null;
			try {
				colMetaRs = dbMeta.getColumns(conn.getCatalog(), schemaPattern, tableMeta.name, null);
				while (colMetaRs.next()) {
					ColumnMeta columnMeta = new ColumnMeta();
					columnMeta.name = colMetaRs.getString("COLUMN_NAME");
					columnMeta.remarks = colMetaRs.getString("REMARKS");
					columnMetaMap.put(columnMeta.name, columnMeta);
				}
			} catch (Exception e) {
				System.out.println("无法生成 REMARKS");
			} finally {
				if (colMetaRs != null) {
					colMetaRs.close();
				}
			}
		}

		for (int i = 1; i <= columnCount; i++) {
			ColumnMeta cm = new ColumnMeta();
			cm.name = rsmd.getColumnName(i);

			String typeStr = null;
			if (dialect.isKeepByteAndShort()) {
				int type = rsmd.getColumnType(i);
				if (type == Types.TINYINT) {
					typeStr = "java.lang.Byte";
				} else if (type == Types.SMALLINT) {
					typeStr = "java.lang.Short";
				}
			}

			if (typeStr == null) {
				String colClassName = rsmd.getColumnClassName(i);
				typeStr = typeMapping.getType(colClassName);
			}

			if (typeStr == null) {
				int type = rsmd.getColumnType(i);
				if (type == Types.BINARY || type == Types.VARBINARY || type == Types.LONGVARBINARY
						|| type == Types.BLOB) {
					typeStr = "byte[]";
				} else if (type == Types.CLOB || type == Types.NCLOB) {
					typeStr = "java.lang.String";
				}
				// 支持 oracle 的 TIMESTAMP、DATE 字段类型，其中 Types.DATE 值并不会出现
				// 保留对 Types.DATE 的判断，一是为了逻辑上的正确性、完备性，二是其它类型的数据库可能用得着
				else if (type == Types.TIMESTAMP || type == Types.DATE) {
					typeStr = "java.util.Date";
				}
				// 支持 PostgreSql 的 jsonb json
				else if (type == Types.OTHER) {
					typeStr = "java.lang.Object";
				} else {
					typeStr = "java.lang.String";
				}
			}

			typeStr = handleJavaType(typeStr, rsmd, i);

			cm.javaType = typeStr;

			// 构造字段对应的属性名 attrName
			cm.attrName = buildAttrName(cm.name);
			// 备注字段赋值
			if (generateRemarks && columnMetaMap.containsKey(cm.name)) {
				cm.remarks = columnMetaMap.get(cm.name).remarks;
			}
			colMap.put((tableMeta.baseModelName + "_" + cm.name).toLowerCase(), cm);
			tableMeta.columnMetas.add(cm);
		}

		rs.close();
		stm.close();

	}

	/**
	 * 构造 modelName，mysql 的 tableName 建议使用小写字母，多单词表名使用下划线分隔，不建议使用驼峰命名 oracle 之下的
	 * tableName 建议使用下划线分隔多单词名，无论 mysql还是 oralce，tableName 都不建议使用驼峰命名
	 */
	@Override
	protected String buildModelName(String tableName) {
		// 移除表名前缀仅用于生成 modelName、baseModelName，而 tableMeta.name 表名自身不能受影响
		tableName = tableName.toLowerCase();
		if (removedTableNamePrefixes != null) {
			for (String prefix : removedTableNamePrefixes) {
				if (tableName.startsWith(prefix.toLowerCase())) {
					tableName = tableName.replaceFirst(prefix.toLowerCase(), "");
					break;
				}
			}
		}

		return StrKit.firstCharToUpperCase(StrKit.toCamelCase(tableName));
	}

	@Override
	protected String handleJavaType(String typeStr, ResultSetMetaData rsmd, int column) throws SQLException {
		if ("java.lang.String".equals(typeStr)) {
			int scale = rsmd.getScale(column); // 小数点右边的位数，值为 0 表示整数
			int precision = rsmd.getPrecision(column); // 最大精度
			if (scale == 0 && precision == 1 && JBoltProjectGenConfig.charToBoolean) {
				typeStr = "java.lang.Boolean";
			}
		}
		// 当前实现只处理 Oracle
		if (!dialect.isOracle()) {
			return typeStr;
		}

		// 默认实现只处理 BigDecimal 类型
		if ("java.math.BigDecimal".equals(typeStr)) {
			int scale = rsmd.getScale(column); // 小数点右边的位数，值为 0 表示整数
			int precision = rsmd.getPrecision(column); // 最大精度
			if (scale == 0) {
				if (precision <= 9) {
					typeStr = "java.lang.Integer";
				} else if (precision <= 19) {
					typeStr = "java.lang.Long";
				} else {
					typeStr = "java.math.BigDecimal";
				}
			} else {
				// 非整数都采用 BigDecimal 类型，需要转成 double 的可以覆盖并改写下面的代码
				typeStr = "java.math.BigDecimal";
			}
		}

		return typeStr;
	}

	// 移除没有主键的 table
	@Override
	protected void removeNoPrimaryKeyTable(List<TableMeta> ret) {
		for (java.util.Iterator<TableMeta> it = ret.iterator(); it.hasNext();) {
			TableMeta tm = it.next();
			if (StrUtil.isBlank(tm.primaryKey)) {
				if (generateView) {
					tm.primaryKey = dialect.getDefaultPrimaryKey();
					JBoltConsoleUtil.printErrorMessageWithDate("Set primaryKey \"" + tm.primaryKey + "\" for " + tm.name);
				} else {
					it.remove();
					JBoltConsoleUtil.printErrorMessageWithDate(" Skip table " + tm.name + " because there is no primary key");
				}
			}
		}
	}

	@Override
	protected ResultSet getTablesResultSet() throws SQLException {
		if (schemaPattern != null) {
			if (generateView) {
				return dbMeta.getTables(conn.getCatalog(), schemaPattern, null, new String[]{"TABLE", "VIEW"});
			} else {
				return dbMeta.getTables(conn.getCatalog(), schemaPattern, null, new String[]{"TABLE"});	// 不支持 view 生成
			}
		}
		return super.getTablesResultSet();
	}

	@Override
	protected void buildTableNames(List<TableMeta> ret) throws SQLException {
		ResultSet rs = getTablesResultSet();
		while (rs.next()) {
			String tableName = rs.getString("TABLE_NAME");

			// 如果使用白名单（size>0），则不在白名单之中的都将被过滤
			if (whitelist.size() > 0 && !whitelist.contains(tableName)) {
				JBoltConsoleUtil.printMessageWithDate(" Skip table(not in whitelist) :" + tableName);
				continue ;
			}
			// 如果使用黑名单（size>0），则处在黑名单之中的都将被过滤
			if (blacklist.size() > 0 && blacklist.contains(tableName)) {
				JBoltConsoleUtil.printMessageWithDate(" Skip table(in blacklist) :" + tableName);
				continue ;
			}

			// isSkipTable 为最早期的过滤机制，建议使用白名单、黑名单过滤
			if (isSkipTable(tableName)) {
				JBoltConsoleUtil.printMessageWithDate(" Skip table(isSkipTable) :" + tableName);
				continue ;
			}

			// jfinal 4.3 新增过滤 table 机制
			if (tableSkip != null && tableSkip.test(tableName)) {
				JBoltConsoleUtil.printMessageWithDate("Skip table :" + tableName);
				continue ;
			}

			TableMeta tableMeta = new TableMeta();
			tableMeta.name = tableName;
			tableMeta.remarks = rs.getString("REMARKS");

			tableMeta.modelName = buildModelName(tableName);
			tableMeta.baseModelName = buildBaseModelName(tableMeta.modelName);
			ret.add(tableMeta);
		}
		rs.close();
	}

	public String getSchemaPattern() {
		return schemaPattern;
	}

	public void setSchemaPattern(String schemaPattern) {
		this.schemaPattern = schemaPattern;
	}

	/**
	 * 转驼峰处理
	 */
	@Override
	protected String buildAttrName(String colName) {
		// 如果设置了自定义转换规则 就执行自定义规则
		if (JBoltProjectGenConfig.columnTobuildAttrNameFun != null) {
			return JBoltProjectGenConfig.columnTobuildAttrNameFun.build(colName);
		}
		// 只有包含下滑线的才转驼峰
		if (colName.indexOf("_") != -1) {
			return StrKit.toCamelCase(colName.toLowerCase());
		}
		// 如果全部是大写的就转为小写
		if (StrUtil.isUpperCase(colName)) {
			return colName.toLowerCase();
		}
		// 如果既没有下划线 有不全都是大写 就直接返回使用了
		return colName;
	}

}
